package cn.lili.modules.connect.serviceimpl;

import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.lili.cache.Cache;
import cn.lili.cache.CachePrefix;
import cn.lili.common.context.ThreadContextHolder;
import cn.lili.common.enums.ClientTypeEnum;
import cn.lili.common.enums.ResultCode;
import cn.lili.common.exception.ServiceException;
import cn.lili.common.security.AuthUser;
import cn.lili.common.security.context.UserContext;
import cn.lili.common.security.token.Token;
import cn.lili.common.utils.CookieUtil;
import cn.lili.common.utils.HttpUtils;
import cn.lili.modules.connect.entity.Connect;
import cn.lili.modules.connect.entity.dto.ConnectAuthUser;
import cn.lili.modules.connect.entity.dto.WechatMPLoginParams;
import cn.lili.modules.connect.entity.enums.ConnectEnum;
import cn.lili.modules.connect.mapper.ConnectMapper;
import cn.lili.modules.connect.service.ConnectService;
import cn.lili.modules.member.entity.dos.Member;
import cn.lili.modules.member.entity.dos.MemberPointsHistory;
import cn.lili.modules.member.entity.dto.ConnectQueryDTO;
import cn.lili.modules.member.entity.enums.PointTypeEnum;
import cn.lili.modules.member.mapper.MemberMapper;
import cn.lili.modules.member.service.MemberPointsHistoryService;
import cn.lili.modules.member.service.MemberService;
import cn.lili.modules.member.token.MemberTokenGenerate;
import cn.lili.modules.system.entity.dos.Setting;
import cn.lili.modules.system.entity.dto.connect.WechatConnectSetting;
import cn.lili.modules.system.entity.dto.connect.dto.WechatConnectSettingItem;
import cn.lili.modules.system.entity.enums.SettingEnum;
import cn.lili.modules.system.service.SettingService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.naming.NoPermissionException;
import java.nio.charset.StandardCharsets;
import java.security.AlgorithmParameters;
import java.security.Security;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 联合登陆接口实现
 *
 * @author Chopper
 */
@Slf4j
@Service
public class ConnectServiceImpl extends ServiceImpl<ConnectMapper, Connect> implements ConnectService {

    static final boolean AUTO_REGION = true;

    @Autowired
    private SettingService settingService;
    @Autowired
    private MemberService memberService;
    @Autowired
    private MemberMapper memberMapper;
    @Autowired
    private MemberTokenGenerate memberTokenGenerate;
    @Autowired
    private Cache cache;
    @Autowired
    private MemberPointsHistoryService memberPointsHistoryService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Token unionLoginCallback(String type, String unionid, String uuid, boolean longTerm) throws NoPermissionException {

        try {
            LambdaQueryWrapper<Connect> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(Connect::getUnionId, unionid);
            queryWrapper.eq(Connect::getUnionType, type);
            //查询绑定关系
            Connect connect = this.getOne(queryWrapper);
            if (connect == null) {
                throw new NoPermissionException("未绑定用户");
            }
            //查询会员
            Member member = memberService.getById(connect.getUserId());
            //如果未绑定会员，则把刚才查询到的联合登录表数据删除
            if (member == null) {
                this.remove(queryWrapper);
                throw new NoPermissionException("未绑定用户");
            }
            return memberTokenGenerate.createToken(member, longTerm);
        } catch (NoPermissionException e) {
            throw e;
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Token unionLoginCallback(String type, ConnectAuthUser authUser, String uuid) {

        Token token;
        try {
            token = this.unionLoginCallback(type, authUser.getUuid(), uuid, false);
        } catch (NoPermissionException e) {
            if (AUTO_REGION) {
                token = memberService.autoRegister(authUser);
                return token;
            } else {
                //写入cookie
                CookieUtil.addCookie(CONNECT_COOKIE, uuid, 1800, ThreadContextHolder.getHttpResponse());
                CookieUtil.addCookie(CONNECT_TYPE, type, 1800, ThreadContextHolder.getHttpResponse());
                //自动登录失败，则把信息缓存起来
                cache.put(ConnectService.cacheKey(type, uuid), authUser, 30L, TimeUnit.MINUTES);
                throw new ServiceException(ResultCode.USER_NOT_BINDING);
            }
        } catch (Exception e) {
            log.error("联合登陆异常：", e);
            throw new ServiceException(ResultCode.ERROR);
        }
        return token;
    }

    @Override
    public void bind(String unionId, String type) {
        AuthUser authUser = UserContext.getCurrentUser();
        Connect connect = new Connect(authUser.getId(), unionId, type);
        this.save(connect);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void unbind(String type) {

        LambdaQueryWrapper<Connect> queryWrapper = new LambdaQueryWrapper<>();

        queryWrapper.eq(Connect::getUserId, UserContext.getCurrentUser().getId());
        queryWrapper.eq(Connect::getUnionType, type);

        this.remove(queryWrapper);
    }

    @Override
    public List<String> bindList() {
        LambdaQueryWrapper<Connect> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Connect::getUserId, UserContext.getCurrentUser().getId());
        List<Connect> connects = this.list(queryWrapper);
        List<String> keys = new ArrayList<>();
        connects.forEach(item -> keys.add(item.getUnionType()));
        return keys;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Token appLoginCallback(ConnectAuthUser authUser, String uuid) {
        try {
            return this.unionLoginCallback(authUser.getSource(), authUser.getUuid(), uuid, true);
        } catch (NoPermissionException e) {
            return memberService.autoRegister(authUser);
        }
    }


    @Override
    public Token miniProgramAutoLogin(WechatMPLoginParams params) {

        Map<String, String> cacheData = (Map<String, String>) cache.get(CachePrefix.WECHAT_SESSION_PARAMS.getPrefix() + params.getUuid());
        Map<String, String> map = new HashMap<>(3);
        //得到微信小程序联合登陆信息
        JSONObject json = this.getConnect(params.getCode());
        //存储session key 后续登录用得到
        String sessionKey = json.getStr("session_key");
        String unionId = json.getStr("unionid");
        String openId = json.getStr("openid");
        map.put("sessionKey", sessionKey);
        map.put("unionId", unionId);
        map.put("openId", openId);
        if (sessionKey == null) {
            try {
                throw new Exception("微信获取openId错误，请返回重试");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        cache.put(CachePrefix.WECHAT_SESSION_PARAMS.getPrefix() + params.getUuid(), map, 900L);
//        if (cacheData == null || cacheData.size()==0) {
//            //得到微信小程序联合登陆信息
//            JSONObject json = this.getConnect(params.getCode());
//            //存储session key 后续登录用得到
//            String sessionKey = json.getStr("session_key");
//            String unionId = json.getStr("unionid");
//            String openId = json.getStr("openid");
//            map.put("sessionKey", sessionKey);
//            map.put("unionId", unionId);
//            map.put("openId", openId);
//            cache.put(CachePrefix.WECHAT_SESSION_PARAMS.getPrefix() + params.getUuid(), map, 900L);
//        } else {
//            map = (Map<String, String>) cacheData;
//        }
        //微信联合登陆参数
        Token token = phoneMpBindAndLogin(map.get("sessionKey"), params, map.get("openId"), map.get("unionId"), params.getShareUserId());
        token.setIsshenghe(false);
        if (params.getVersion() != null && params.getVersion().length() > 0) {
            QueryWrapper<Member> queryMember = new QueryWrapper<>();
            queryMember.eq("version", params.getVersion());
            queryMember.eq("mobile", "18428316818");
            queryMember.last(" limit 1");
            Member mm = memberMapper.selectOne(queryMember);
            if (mm != null) {
                token.setIsshenghe(true);
            }
        }
        return token;
    }

    /**
     * 通过微信返回等code 获取openid 等信息
     *
     * @param code
     * @return
     */
    public JSONObject getConnect(String code) {
        WechatConnectSettingItem setting = getWechatMPSetting();
        String url = "https://api.weixin.qq.com/sns/jscode2session?" +
                "appid=" + setting.getAppId() + "&" +
                "secret=" + setting.getAppSecret() + "&" +
                "js_code=" + code + "&" +
                "grant_type=authorization_code";
        String content = HttpUtils.doGet(url, "UTF-8", 3000, 3000);
        log.error(content);
        return JSONUtil.parseObj(content);
    }

    /**
     * 手机号 绑定 且 自动登录
     *
     * @param sessionKey 微信sessionKey
     * @param params     微信小程序自动登录参数
     * @param openId     微信openid
     * @param unionId    微信unionid
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public Token phoneMpBindAndLogin(String sessionKey, WechatMPLoginParams params, String openId, String unionId, String shareUserId) {
        String encryptedData = params.getEncryptedData(), iv = params.getIv();
        Member newMember = null;
        boolean isnew = false;
        if(shareUserId==null){
            shareUserId="";
        }
        if (encryptedData != null) {
            JSONObject userInfo = this.getUserInfo(encryptedData, sessionKey, iv);
            log.info("联合登陆返回：{}", userInfo.toString());
            String phone = (String) userInfo.get("purePhoneNumber");
            //手机号登录
            LambdaQueryWrapper<Member> lambdaQueryWrapper = new LambdaQueryWrapper<>();
            lambdaQueryWrapper.eq(Member::getMobile, phone);
            Member member = memberService.getOne(lambdaQueryWrapper);
            //如果不存在会员，则进行绑定微信openid 和 unionid，并且登录
            if (member != null) {
                bindMpMember(openId, unionId, member);
                return memberTokenGenerate.createToken(member, true);
            }

            //如果没有会员，则根据手机号注册会员
            newMember = new Member("m" + phone, "111111", phone, params.getNickName(), params.getImage());
            if(shareUserId.length()>0){
                newMember.setYaoqingren(Long.parseLong(shareUserId));
                newMember.setYaoqingshijian(new Date());
            }
            memberService.save(newMember);
            newMember = memberService.findByUsername(newMember.getUsername());

            bindMpMember(openId, unionId, newMember);
            isnew = true;
        } else {
            LambdaQueryWrapper<Connect> lambdaQueryWrapper = new LambdaQueryWrapper();
            lambdaQueryWrapper.eq(Connect::getUnionId, openId);
            lambdaQueryWrapper.eq(Connect::getUnionType, ConnectEnum.WECHAT_MP_OPEN_ID.name());
            List<Connect> connects = this.list(lambdaQueryWrapper);
            if (connects != null && connects.size() > 0) {
                newMember = memberService.getById(connects.get(0).getUserId());
            } else {
                newMember = new Member("m" + openId, "111111", openId, params.getNickName(), params.getImage());

            }
        }
        if (isnew && shareUserId != null && shareUserId.length() > 0) {
            QueryWrapper<Member> queryaoqing=new QueryWrapper<>();
            queryaoqing.eq("yaoqingren",shareUserId);
            Long count=memberService.count(queryaoqing);
            if(count==6){
                Member member = memberService.getById(shareUserId);
                if (member != null) {
                    Long point = 200L;
                    //积分变动后的会员积分
                    long currentPoint;
                    //会员总获得积分
                    long totalPoint = member.getTotalPoint();
                    //如果增加积分
                    currentPoint = member.getPoint() + point;
                    //如果是增加积分 需要增加总获得积分
                    totalPoint = totalPoint + point;
                    member.setPoint(currentPoint);
                    member.setTotalPoint(totalPoint);
                    boolean result = memberService.updateById(member);
                    String type = PointTypeEnum.INCREASE.name();
                    MemberPointsHistory memberPointsHistory = new MemberPointsHistory();
                    memberPointsHistory.setMemberId(member.getId());
                    memberPointsHistory.setMemberName(member.getUsername());
                    memberPointsHistory.setPointType(type);
                    memberPointsHistory.setVariablePoint(point);
                    memberPointsHistory.setBeforePoint(member.getPoint() - point);
                    memberPointsHistory.setType(3);
                    memberPointsHistory.setPoint(member.getPoint());
                    memberPointsHistory.setContent("系统活动邀请6位新用户赠送");
                    memberPointsHistory.setCreateBy("系统");
                    memberPointsHistoryService.save(memberPointsHistory);
                }
            }
            Member member = memberService.getById(shareUserId);
//            if (member != null) {
//                Long point = 5L;
//                //积分变动后的会员积分
//                long currentPoint;
//                //会员总获得积分
//                long totalPoint = member.getTotalPoint();
//                //如果增加积分
//                currentPoint = member.getPoint() + point;
//                //如果是增加积分 需要增加总获得积分
//                totalPoint = totalPoint + point;
//                member.setPoint(currentPoint);
//                member.setTotalPoint(totalPoint);
//                boolean result = memberService.updateById(member);
//                String type = PointTypeEnum.INCREASE.name();
//                MemberPointsHistory memberPointsHistory = new MemberPointsHistory();
//                memberPointsHistory.setMemberId(member.getId());
//                memberPointsHistory.setMemberName(member.getUsername());
//                memberPointsHistory.setPointType(type);
//                memberPointsHistory.setVariablePoint(point);
//                memberPointsHistory.setBeforePoint(member.getPoint() + point);
//                memberPointsHistory.setType(3);
//                memberPointsHistory.setPoint(member.getPoint());
//                memberPointsHistory.setContent("邀请新用户注册获得积分");
//                memberPointsHistory.setCreateBy("系统");
//                memberPointsHistoryService.save(memberPointsHistory);
//            }
        }


        return memberTokenGenerate.createToken(newMember, true);
    }

    @Override
    public Connect queryConnect(ConnectQueryDTO connectQueryDTO) {

        LambdaQueryWrapper<Connect> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(CharSequenceUtil.isNotEmpty(connectQueryDTO.getUserId()), Connect::getUserId, connectQueryDTO.getUserId())
                .eq(CharSequenceUtil.isNotEmpty(connectQueryDTO.getUnionType()), Connect::getUnionType, connectQueryDTO.getUnionType())
                .eq(CharSequenceUtil.isNotEmpty(connectQueryDTO.getUnionId()), Connect::getUnionId, connectQueryDTO.getUnionId());
        return this.getOne(queryWrapper);
    }

    @Override
    public void deleteByMemberId(String userId) {
        LambdaQueryWrapper<Connect> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Connect::getUserId, userId);
        this.remove(queryWrapper);
    }

    /**
     * 会员绑定 绑定微信小程序
     * <p>
     * 如果openid 已经绑定其他账号，则这里不作处理，如果未绑定，则绑定最新的会员
     * 这样，微信小程序注册之后，其他app 公众号页面，都可以实现绑定自动登录功能
     * </p>
     *
     * @param openId
     * @param unionId
     * @param member
     */
    private void bindMpMember(String openId, String unionId, Member member) {


        //如果unionid 不为空  则为账号绑定unionid
        if (CharSequenceUtil.isNotEmpty(unionId)) {
            LambdaQueryWrapper<Connect> lambdaQueryWrapper = new LambdaQueryWrapper();
            lambdaQueryWrapper.eq(Connect::getUnionId, unionId);
            lambdaQueryWrapper.eq(Connect::getUnionType, ConnectEnum.WECHAT.name());
            List<Connect> connects = this.list(lambdaQueryWrapper);
            if (connects.size() == 0) {
                Connect connect = new Connect();
                connect.setUnionId(unionId);
                connect.setUserId(member.getId());
                connect.setUnionType(ConnectEnum.WECHAT.name());
                this.save(connect);
            }
        }//如果openid 不为空  则为账号绑定openid
        if (CharSequenceUtil.isNotEmpty(openId)) {
            LambdaQueryWrapper<Connect> lambdaQueryWrapper = new LambdaQueryWrapper();
            lambdaQueryWrapper.eq(Connect::getUnionId, openId);
            lambdaQueryWrapper.eq(Connect::getUnionType, ConnectEnum.WECHAT_MP_OPEN_ID.name());
            List<Connect> connects = this.list(lambdaQueryWrapper);
            if (connects.isEmpty()) {
                Connect connect = new Connect();
                connect.setUnionId(openId);
                connect.setUserId(member.getId());
                connect.setUnionType(ConnectEnum.WECHAT_MP_OPEN_ID.name());
                this.save(connect);
            }
        }

    }

    /**
     * 获取微信小程序配置
     *
     * @return 微信小程序配置
     */
    private WechatConnectSettingItem getWechatMPSetting() {
        Setting setting = settingService.get(SettingEnum.WECHAT_CONNECT.name());

        WechatConnectSetting wechatConnectSetting = JSONUtil.toBean(setting.getSettingValue(), WechatConnectSetting.class);

        if (wechatConnectSetting == null) {
            throw new ServiceException(ResultCode.WECHAT_CONNECT_NOT_EXIST);
        }
        //寻找对应对微信小程序登录配置
        for (WechatConnectSettingItem wechatConnectSettingItem : wechatConnectSetting.getWechatConnectSettingItems()) {
            if (wechatConnectSettingItem.getClientType().equals(ClientTypeEnum.WECHAT_MP.name())) {
                return wechatConnectSettingItem;
            }
        }

        throw new ServiceException(ResultCode.WECHAT_CONNECT_NOT_EXIST);
    }


    /**
     * 解密，获取微信信息
     *
     * @param encryptedData 加密信息
     * @param sessionKey    微信sessionKey
     * @param iv            微信揭秘参数
     * @return 用户信息
     */
    public JSONObject getUserInfo(String encryptedData, String sessionKey, String iv) {

        log.info("encryptedData:{},sessionKey:{},iv:{}", encryptedData, sessionKey, iv);
        //被加密的数据
        byte[] dataByte = Base64.getDecoder().decode(encryptedData);
        //加密秘钥
        byte[] keyByte = Base64.getDecoder().decode(sessionKey);
        //偏移量
        byte[] ivByte = Base64.getDecoder().decode(iv);
        try {
            //如果密钥不足16位，那么就补足.  这个if 中的内容很重要
            int base = 16;
            if (keyByte.length % base != 0) {
                int groups = keyByte.length / base + (keyByte.length % base != 0 ? 1 : 0);
                byte[] temp = new byte[groups * base];
                Arrays.fill(temp, (byte) 0);
                System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
                keyByte = temp;
            }
            //初始化
            Security.addProvider(new BouncyCastleProvider());
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
            SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
            AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
            parameters.init(new IvParameterSpec(ivByte));
            //初始化
            cipher.init(Cipher.DECRYPT_MODE, spec, parameters);
            byte[] resultByte = cipher.doFinal(dataByte);
            if (null != resultByte && resultByte.length > 0) {
                String result = new String(resultByte, StandardCharsets.UTF_8);
                return JSONUtil.parseObj(result);
            }
        } catch (Exception e) {
            log.error("解密，获取微信信息错误", e);
        }
        throw new ServiceException(ResultCode.USER_CONNECT_ERROR);
    }
}